/*
 * File: darray.c
 * Author:  Yin Lijun <yinlijun2004@gmail.com>
 * Brief:   darray interface.
 *
 * Copyright (c) 2011 - 2012  Yin Lijun <yinlijun2004@gmail.com>
 *
 * Licensed under the Academic Free License version 2.1
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * History:
 * ================================================================
 * 2012-03-12 Yin Lijun <yinlijun2004@gmail.com> created
 *
 */
#include "darray.h"

#define BASE_ARRAY_SIZE 25 
#define SIZE_EXPAND_FACTOR 1.5

struct _DArray{
	void** data;
	size_t cur_length;
	size_t max_length;
	DestoryFunc dest_func;
};
static Ret expand_darray(DArray* thiz)
{
	size_t new_size = SIZE_EXPAND_FACTOR * thiz->max_length;
	thiz->data = (void **)realloc(thiz->data, new_size * sizeof(void *));
	return_if_fail_val(thiz->data != NULL, RET_OVERFLOW);
	thiz->max_length = new_size;

	return RET_OK;
}

DArray* darray_create(DestoryFunc dest)
{
	DArray* thiz = (DArray *)malloc(sizeof(DArray));
	return_if_fail_val(thiz != NULL, NULL);
	thiz->dest_func = dest;
	thiz->data = (void **)calloc(1, BASE_ARRAY_SIZE * sizeof(void *));
	if(thiz->data == NULL) 
	{
		free(thiz);

		return NULL;
	}
	thiz->max_length = BASE_ARRAY_SIZE;
	thiz->cur_length = 0;

	return thiz;
}

Ret darray_insert(DArray* thiz, void* data, size_t index)
{
	return_if_fail_val(thiz != NULL
					&& index <= thiz->cur_length,
				   	RET_PARAM_ERROR);
	size_t pivot = thiz->cur_length;
	void** pdata = thiz->data;

	while(pivot >= index) 
	{
		pdata[pivot + 1] = pdata[pivot];
		if(pivot == 0)
		{
			//caution here, pivot my overflow
			break;
		}
		pivot--;
	}

	pdata[index] = data;
	thiz->cur_length++;
	
	if(thiz->cur_length == thiz->max_length - 1)
	{
		expand_darray(thiz);	
	}

	return RET_OK;
}

Ret darray_prepend(DArray* thiz, void* data)
{
	return darray_insert(thiz, data, 0);
}

Ret darray_append(DArray* thiz, void* data)
{
	return darray_insert(thiz, data, thiz->cur_length);
}

Ret darray_get_data_by_index(DArray* thiz, size_t index, void** data)
{
	return_if_fail_val(thiz != NULL
					&& index < thiz->cur_length
					&& data != NULL,
					RET_PARAM_ERROR);

	*data = thiz->data[index];

	return RET_OK;
}

Ret darray_delete(DArray* thiz, size_t index)
{
	return_if_fail_val(thiz != NULL
				&& index < thiz->cur_length,
				RET_PARAM_ERROR);
	size_t max = thiz->cur_length - 1;
	void** pdata = thiz->data;
	while(index < max)
	{
		pdata[index] = pdata[index + 1];
		index++;
	}
	thiz->cur_length--;
	/* Do not call thiz->dest_func(thiz->data[index]) here
	 * Because maybe user will use data,
	 * so need user to free data explicity
	 * */
	return RET_OK;
}

size_t darray_get_length(DArray* thiz)
{
	return_if_fail_val(thiz != NULL, 0);

	return thiz->cur_length;
}

Ret darray_foreach(DArray* thiz, VisitFunc visit, void* ctx)
{
	return_if_fail_val(thiz != NULL
					&& visit != NULL,
					RET_PARAM_ERROR);
	
	size_t index = 0;
	void** pdata = thiz->data;
	while(index < thiz->cur_length)
	{
		visit(ctx, pdata[index]);
		index++;
	}

	return RET_OK;
}

Ret darray_destroy(DArray* thiz)
{
	return_if_fail_val(thiz != NULL, RET_PARAM_ERROR);
	size_t index = 0;
	void** pdata = thiz->data;
	if(thiz->dest_func != NULL) 
	{
		while(index < thiz->cur_length)
		{
			thiz->dest_func(pdata[index]);
			index++;
		}
	}
	free(pdata);
	free(thiz);

	return RET_OK;
}

#ifdef __DARRAY_TEST__
#include <stdio.h>
#include <assert.h>
#define TEST_SIZE 1000
void int_visit(void* ctx, void* data)
{
	printf("%4d", (int)data);
}

int main(int argc, char** argv)
{
	DArray* darray = darray_create(NULL);
	assert(darray != NULL);
	printf("\ntest darray_prepend\n");
	size_t length = 0;
	int i = 0;
	int j = 0;
	for(; i < TEST_SIZE; i++)
	{
		assert(darray_prepend(darray, (void *)i) == RET_OK);
		assert(darray_get_length(darray) == ++length);
	   	assert(darray_get_data_by_index(darray, 0, (void **)&j) == RET_OK);
		assert(j == i);
	}
	assert(darray_foreach(darray, int_visit, NULL) == RET_OK);
	
	printf("\ntest darray_append\n");
	for(--i; i >= 0; i--)
	{
		assert(darray_append(darray, (void *)i) == RET_OK);
		assert(darray_get_length(darray) == ++length);
		assert(darray_get_data_by_index(darray, darray_get_length(darray) - 1, (void **)&j) == RET_OK);
		assert(j == i);	
	}
	assert(darray_foreach(darray, int_visit, NULL) == RET_OK);

	printf("\ntest darray_insert\n");
	for(i = 0; i < TEST_SIZE; i++)
	{
		assert(darray_insert(darray, (void *)i, TEST_SIZE) == RET_OK);
		assert(darray_get_length(darray) == ++length);
		assert(darray_get_data_by_index(darray, TEST_SIZE, (void **)&j) == RET_OK);
		assert(j == i);
	}
	assert(darray_foreach(darray, int_visit, NULL) == RET_OK);

	printf("\ntest darray_delete\n");
	for(i = 0; i < TEST_SIZE; i ++)
	{
		assert(darray_delete(darray, TEST_SIZE) == RET_OK);
		assert(darray_get_length(darray) == --length);
	}
	assert(darray_foreach(darray, int_visit, NULL) == RET_OK);

	printf("\ntest darray_delete\n");
	for(i = 0; i < TEST_SIZE; i ++)
	{
		assert(darray_delete(darray, 0) == RET_OK);
		assert(darray_get_length(darray) == --length);
	}
	assert(darray_foreach(darray, int_visit, NULL) == RET_OK);

	printf("\ntest darray_destroy\n");
	assert(darray_destroy(darray) == RET_OK);

	return 0;
}
#endif
